package scheduler

import (
	"fmt"
	"math"
	"product/common/validator"
	"strconv"
	"strings"
	"time"
)

// ParserOption 解析配置项
type ParserOption int

// 支持的表达式
const (
	Second         ParserOption = 1 << iota // 秒
	SecondOptional                          // 可选
	Minute                                  // 分
	Hour                                    // 时
	Dom                                     // 日
	Month                                   // 月
	Dow                                     // 星期
	DowOptional                             // 可选
	Descriptor                              // 表达式
)

// places 支持的表达式位置
var places = []ParserOption{
	Second,
	Minute,
	Hour,
	Dom,
	Month,
	Dow,
}

// 默认值
var defaults = []string{"0", "0", "0", "*", "*", "*"}

// 私有变量
var standardParser = NewParser(Minute | Hour | Dom | Month | Dow | Descriptor)

// Parser 解析器
type Parser struct {
	options ParserOption
}

// NewParser 创建一个解析器
func NewParser(options ParserOption) *Parser {
	optionals := 0
	if options&DowOptional > 0 {
		optionals++
	}
	if options&SecondOptional > 0 {
		optionals++
	}
	if optionals > 1 {
		panic("多个可选项没有配置")
	}
	return &Parser{options: options}
}

// Parse 解析表达式
func (p *Parser) Parse(expression string) (ISchedule, error) {
	if validator.IsEmptyString(expression) {
		return nil, fmt.Errorf("表达式不能为空")
	}

	// 获取当前时区
	location := time.Local
	// 判断表达式是否包含时区
	if strings.HasPrefix(expression, "TZ=") || strings.HasPrefix(expression, "CRON_TZ=") {
		var err error
		i := strings.Index(expression, " ")
		eq := strings.Index(expression, "=")
		if location, err = time.LoadLocation(expression[eq+1 : i]); err != nil {
			return nil, fmt.Errorf("时区解析失败: %s", err)
		}
		expression = strings.TrimSpace(expression[i:])
	}

	if strings.HasPrefix(expression, "@") {
		if p.options&Descriptor == 0 {
			return nil, fmt.Errorf("不支持的表达式: %s", expression)
		}
		return parseExpression(expression, location)
	}

	// 空格分割
	fields := strings.Fields(expression)

	var err error
	fields, err = normalizeFields(fields, p.options)
	if err != nil {
		return nil, err
	}
	field := func(field string, r bounds) uint64 {
		if err != nil {
			return 0
		}
		var bits uint64
		bits, err = parseField(field, r)
		return bits
	}

	var (
		second     = field(fields[0], seconds)
		minute     = field(fields[1], minutes)
		hour       = field(fields[2], hours)
		dayOfMonth = field(fields[3], dom)
		month      = field(fields[4], months)
		dayOfWeek  = field(fields[5], dow)
	)
	if err != nil {
		return nil, err
	}
	return &ExpressionSchedule{
		Second:   second,
		Minute:   minute,
		Hour:     hour,
		Dom:      dayOfMonth,
		Month:    month,
		Dow:      dayOfWeek,
		Location: location,
	}, nil

}

/**
* ##### 私有方法 ######
**/

func normalizeFields(fields []string, options ParserOption) ([]string, error) {
	optional := 0
	if options&SecondOptional > 0 {
		options |= Second
		optional++
	}
	if options&DowOptional > 0 {
		options |= Dow
		optional++
	}
	if optional > 1 {
		return nil, fmt.Errorf("多个可选项没有配置")
	}
	mx := 0
	for _, place := range places {
		if options&place > 0 {
			mx++
		}
	}
	mi := mx - optional

	if count := len(fields); count < mi || count > mx {
		if mi == mx {
			return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", mi, count, fields)
		}
		return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", mi, mx, count, fields)
	}

	// Populate the optional field if not provided
	if mi < mx && len(fields) == mi {
		switch {
		case options&DowOptional > 0:
			fields = append(fields, defaults[5]) // TODO: improve access to default
		case options&SecondOptional > 0:
			fields = append([]string{defaults[0]}, fields...)
		default:
			return nil, fmt.Errorf("unknown optional field")
		}
	}

	// Populate all fields not part of options with their defaults
	n := 0
	expandedFields := make([]string, len(places))
	copy(expandedFields, defaults)
	for i, place := range places {
		if options&place > 0 {
			expandedFields[i] = fields[n]
			n++
		}
	}
	return expandedFields, nil
}

// mustParseInt 解析整数
func mustParseInt(expression string) (uint, error) {
	num, err := strconv.Atoi(expression)
	if err != nil {
		return 0, fmt.Errorf("表达式解析失败: %s", err)
	}
	if num < 0 {
		return 0, fmt.Errorf("表达式数字仅能为正整数")
	}
	return uint(num), nil
}

// parseIntOrName 解析整数或名称
func parseIntOrName(expr string, nameMap map[string]uint) (uint, error) {
	if len(nameMap) <= 0 {
		return mustParseInt(expr)
	}
	if val, ok := nameMap[strings.ToLower(expr)]; ok {
		return val, nil
	}
	return 0, fmt.Errorf("不支持的表达式: %s", expr)
}
func all(r bounds) uint64 {
	return getBits(r.min, r.max, 1) | starBit
}

func getBits(min, max, step uint) uint64 {
	var bits uint64
	if step == 1 {
		return ^(math.MaxUint64 << (max + 1) & (math.MaxUint64 << min))
	}
	for i := min; i <= max; i += step {
		bits |= 1 << i
	}
	return bits
}

// parseExpression 解析表达式
func parseExpression(expression string, location *time.Location) (ISchedule, error) {
	switch expression {
	case "@yearly", "@annually":
		return &ExpressionSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      1 << dom.min,
			Month:    1 << months.min,
			Dow:      all(dow),
			Location: location,
		}, nil
	case "@monthly":
		return &ExpressionSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      1 << dom.min,
			Month:    all(months),
			Dow:      all(dow),
			Location: location,
		}, nil
	case "@weekly":
		return &ExpressionSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      all(dom),
			Month:    all(months),
			Dow:      1 << dow.min,
			Location: location,
		}, nil
	case "@daily", "@midnight":
		return &ExpressionSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     1 << hours.min,
			Dom:      all(dom),
			Month:    all(months),
			Dow:      all(dow),
			Location: location,
		}, nil
	case "@hourly":
		return &ExpressionSchedule{
			Second:   1 << seconds.min,
			Minute:   1 << minutes.min,
			Hour:     all(hours),
			Dom:      all(dom),
			Month:    all(months),
			Dow:      all(dow),
			Location: location,
		}, nil
	}
	const every = "@every"
	if strings.HasPrefix(expression, every) {
		duration, err := time.ParseDuration(expression[len(every):])
		if err != nil {
			return nil, fmt.Errorf("解析@every表达式失败: %s", err)
		}
		return Every(duration), nil
	}
	return nil, fmt.Errorf("不支持的表达式: %s", expression)
}

// parseField 解析字段
func parseField(field string, r bounds) (uint64, error) {
	var bits uint64
	ranges := strings.FieldsFunc(field, func(r rune) bool {
		return r == ','
	})
	for _, str := range ranges {
		bit, err := parseRange(str, r)
		if err != nil {
			return bit, err
		}
		bits |= bit
	}
	return bits, nil
}

// parseRange 解析范围
func parseRange(expr string, r bounds) (uint64, error) {
	var (
		start, end, step uint
		rangeAndStep     = strings.Split(expr, "/")
		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
		singleDigit      = len(lowAndHigh) == 1
		err              error
	)
	var extra uint64
	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
		start = r.min
		end = r.max
		extra = starBit
	} else {
		start, err = parseIntOrName(lowAndHigh[0], r.nameMap)
		if err != nil {
			return 0, err
		}
		switch len(lowAndHigh) {
		case 1:
			end = start
		case 2:
			end, err = parseIntOrName(lowAndHigh[1], r.nameMap)
		default:
			return 0, fmt.Errorf("表达式解析失败: %s", expr)
		}
	}
	switch len(rangeAndStep) {
	case 1:
		step = 1
	case 2:
		step, err = mustParseInt(rangeAndStep[1])
		if err != nil {
			return 0, err
		}

		// Special handling: "N/step" means "N-max/step".
		if singleDigit {
			end = r.max
		}
		if step > 1 {
			extra = 0
		}
	default:
		return 0, fmt.Errorf("too many slashes: %s", expr)
	}

	if start < r.min {
		return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
	}
	if end > r.max {
		return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr)
	}
	if start > end {
		return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
	}
	if step == 0 {
		return 0, fmt.Errorf("step of range should be a positive number: %s", expr)
	}

	return getBits(start, end, step) | extra, nil
}
